//------------------------------------------------------------------------------
// Barbot 2012 project
// This software communicates with a microcontroller through a serial port
// to control our "BarBot"
//------------------------------------------------------------------------------
#include <iostream>

#include <fcntl.h>    // File control definitions
#include <errno.h>    // Error number definitions
#include <termios.h>  // POSIX terminal control definitions
#include <stdio.h>    // Standard input/output definitions
#include <string.h>   // String function definitions
#include <sys/time.h>
#include <unistd.h>   // UNIX standard function definitions

using namespace std;

int fd = 0;

static const unsigned char startByte = 0xFE;
static const unsigned char endByte = 0xFD;
static const unsigned char ready = 0x01;
static const unsigned char ack = 0x0F;
static const unsigned char nack = 0xF0;
static const unsigned char recipe = 0x10;

// Amounts are in ounces x 10
static const unsigned char amount05 = 23; // carefully calibrated
static const unsigned char amount10 = 46; //carefully calibrated
static const unsigned char amount15 = 68; // guesstimation
static const unsigned char amount20 = 88; // guesstimation
static const unsigned char amount25 = 105;
static const unsigned char amount30 = 126;
static const unsigned char amount35 = 147;
static const unsigned char amount40 = 168;
static const unsigned char amount45 = 189;
static const unsigned char amount50 = 210;
static const unsigned char amount55 = 231;
static const unsigned char amount60 = 252;

// This will trigger the wildest fountain and light show you have ever seen
static const unsigned char bellagio = 0xAA; // Reserved

// Message Lengths
static const unsigned char responseLength = 3;
static const unsigned char readyLength = 3;
static const unsigned char recipeLength = 11;

static const int idxVod = 2; // Vodka
static const int idxRum = 3; // Rum
static const int idxWhi = 4; // Whiskey
static const int idxGin = 5; // Gin
static const int idxOju = 6; // Orange Juice
static const int idxLim = 7; // Lime Juice
static const int idxCok = 8; // Coke
static const int idxGib = 9; // Ginger beer

//------------------------------------------------------------------------------
// Hard-coded recipe
// 0xFE, 0x10, ... N values below 0xFD (0-252) ... , 0xFD
// Where N = number of pylons
//------------------------------------------------------------------------------
// Debug
static unsigned char RecipeDebugAll05[11];
static unsigned char RecipeDebugAll10[11];
static unsigned char RecipeDebugVodka[11];
static unsigned char RecipeDebugRum[11];
static unsigned char RecipeDebugWhiskey[11];
static unsigned char RecipeDebugGin[11];
static unsigned char RecipeDebugOj[11];
static unsigned char RecipeDebugLime[11];
static unsigned char RecipeDebugCoke[11];
static unsigned char RecipeDebugGingerBeer[11];

// Mixed drinks
static unsigned char RecipeRumAndCoke[11];
static unsigned char RecipeMoscowMule[11];
static unsigned char RecipeDarkAndStormy[11];
static unsigned char RecipeGingerGin[11];
static unsigned char RecipeGingerWhiskey[11];
static unsigned char RecipeScrewdriver[11];
static unsigned char RecipeKeyWestScrewdriver[11];
static unsigned char RecipeBrassMonkey[11];
static unsigned char RecipeCubaLibre[11];
static unsigned char RecipeGinAndJuice[11];
static unsigned char RecipePanGalactic[11];
static unsigned char RecipeArmySpecial[11];
static unsigned char RecipeHotDamn[11];
static unsigned char RecipeCowBoyRoy[11];
static unsigned char RecipeGimlet[11];
static unsigned char RecipePolo[11];
static unsigned char RecipePeltikatto[11];
static unsigned char RecipeSouthBank[11];


// On the rocks
static unsigned char RecipeVodkaOnTheRocks[11];
static unsigned char RecipeRumOnTheRocks[11];
static unsigned char RecipeWhiskeyOnTheRocks[11];
static unsigned char RecipeGinOnTheRocks[11];


//------------------------------------------------------------------------------
// Pre determined commands
//------------------------------------------------------------------------------
// ready Command - Server uses this to check if the Arduino is ready to accpet
// a recipe
// 0xFE, 0x01, 0xFD
static const unsigned char readyCommand[] = {startByte, ready, endByte};

// Ack Command - microcontroller reply
// 0xFE, 0x0F, 0xFD
static const unsigned char ackCommand[] = {startByte, ack, endByte};

// Nack Command - microcontroller reply
// 0xFE, 0xF0, 0xFD
static const unsigned char nackCommand[] = {startByte, nack, endByte};

//------------------------------------------------------------------------------
// Determine the number of milliseconds difference between two timeval structs
//------------------------------------------------------------------------------
long millisecondDelta(struct timeval start, struct timeval end)
{
  double seconds, uSeconds;
  seconds  = (double)(end.tv_sec  - start.tv_sec);
  uSeconds = (double)(end.tv_usec - start.tv_usec);

  long delta = (long)(((seconds) * 1000.0 + uSeconds/1000.0) + 0.5);
  return delta;
}

//------------------------------------------------------------------------------
// Used for debugging - blocks until timeout
//------------------------------------------------------------------------------
int displayRead()
{  
  struct timeval timeStart;
  gettimeofday(&timeStart, NULL);

  while (1)
  {
    unsigned char buffer[255];
    unsigned char len = 0;
    
    int bytesRead = read(fd, buffer, 255);
    
    if (bytesRead)
    {
      for (int i = 0; i < bytesRead; ++i)  // for all chars in string
      {
        printf("Got: %X \n", buffer[i]);
      }
    }

    usleep(100000);

    struct timeval currentTime;
    gettimeofday(&currentTime, NULL);
    
    long delta = millisecondDelta(timeStart, currentTime);
     
    if (delta > 10000) return -1; // Time out
  }
}

//------------------------------------------------------------------------------
// Block until it receives status information or times out
// Returns:
//     -2 - time out
//     -1 - invalid data
//   0x0F - ACK
//   0xF0 - NACK
//------------------------------------------------------------------------------
int readStatus(long msTimeout)
{
  struct timeval timeStart;
  gettimeofday(&timeStart, NULL);

  while (1)
  {
    unsigned char buffer[255];
    int start = -1;
    unsigned char len = 0;

    int bytesRead = read(fd, buffer, 255);
    
    if (bytesRead)
    {
      // Look for ACK
      for (int i = 0; i < bytesRead; ++i)  // for all chars in string
      {
        // DEBUG
        //printf("Got: %X \n", buffer[i]);
        // We have a start index
        if (start != -1)
        {
          len++;
          if (buffer[i] == endByte)
          {
            // Respond length is all wrong
            if ((i - start + 1) != responseLength)
            {
              // Invalid data
              return -1; 
            }
            
            int response = buffer[start+1];
            switch (response)
            {
              // Acknowledge or negative acknowledge
              case ack:
              case nack: return response;
              // Invalid data
              default: return -1;  
            }
          }
        }
        else // Do not have start yet
        {
          // Found start
          if (buffer[i] == startByte) start = i;
          len = 1;
        }
      }
    }

    // Sleep 100 ms
    usleep(100000);

    struct timeval currentTime;
    gettimeofday(&currentTime, NULL);
    
    long delta = millisecondDelta(timeStart, currentTime);
    
    if (delta > msTimeout)
    {
      // Time out
      return -2; 
    }
  }
}

//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
int isReady()
{
  write(fd, readyCommand, readyLength);
  return readStatus(2000);
}

//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
void writeRecipe(unsigned char* pRecipe)
{
  write(fd, pRecipe, recipeLength);
}

//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
int openPort()
{
  // USB device
  fd = open("/dev/ttyUSB0", O_RDWR | O_NOCTTY | O_NDELAY);

  if (fd == -1)
  {
    // Could not open the port.
    perror("openPort: Unable to open serial port - ");
    return 0;
  }
  else
  {
    fcntl(fd, F_SETFL, 0);
  }
  return 1;
}

//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
void closePort()
{
  if (fd)
  {
    close(fd);
  }
}

//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
void printStatus(int status)
{
  if (status == -1)
  {
    cout << "Invalid data!";
  }
  else if (status == -2)
  {
    cout << "Time out!";
  }
  else if (status == nack)
  {
    cout << "System error!";
  }
  else if (status == ack)
  {
    cout << "OK";
  }
}

//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
void initRecipe(unsigned char* pRecipe)
{
  memset(pRecipe, 0, recipeLength);
  pRecipe[0] = startByte;
  pRecipe[1] = recipe;
  pRecipe[10] = endByte;
}

//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
void initDrinks()
{

  // ----------------------------------
  //             Debug
  // ----------------------------------
  // 0xFC (252) corresponds to 6 oz
  // Debug: All 0.5 oz
  initRecipe(RecipeDebugAll05);
  for (int i = 2; i < 10; i++)
  {
    RecipeDebugAll05[i] = amount05;
  }
  
  initRecipe(RecipeDebugAll10);
  // Debug: All 1 oz
  for (int i = 2; i < 10; i++)
  {
    RecipeDebugAll10[i] = amount10;
  }
  
  // Debug: Just vodka
  initRecipe(RecipeDebugVodka);
  RecipeDebugVodka[idxVod] = amount10;
  
  // Debug: Just rum
  initRecipe(RecipeDebugRum);
  RecipeDebugRum[idxRum] = amount10;
  
  // Debug: Just whiskey
  initRecipe(RecipeDebugWhiskey);
  RecipeDebugWhiskey[idxWhi] = amount10;
  
  // Debug: just gin
  initRecipe(RecipeDebugGin);
  RecipeDebugGin[idxGin] = amount10;
  
  // Debug: Just Oj
  initRecipe(RecipeDebugOj);
  RecipeDebugOj[idxOju] = amount10;
  
  // Debug: Just lime
  initRecipe(RecipeDebugLime);
  RecipeDebugLime[idxLim] = amount05;

  // Debug: Just coke
  initRecipe(RecipeDebugCoke);
  RecipeDebugCoke[idxCok] = amount15;

  // Debug: Just ginger beer
  initRecipe(RecipeDebugGingerBeer);
  RecipeDebugGingerBeer[idxGib] = amount15;

  // ----------------------------------
  //         Actual recipies
  // ----------------------------------
  initRecipe(RecipeRumAndCoke);
  RecipeRumAndCoke[idxRum] = amount15;
  RecipeRumAndCoke[idxCok] = amount50;

  initRecipe(RecipeMoscowMule);
  RecipeMoscowMule[idxVod] = amount20;
  RecipeMoscowMule[idxGib] = amount40;
  RecipeMoscowMule[idxLim] = amount05;
  
  initRecipe(RecipeDarkAndStormy);
  RecipeDarkAndStormy[idxRum] = amount35;
  RecipeDarkAndStormy[idxGib] = amount55;
  
  initRecipe(RecipeGingerGin);
  RecipeGingerGin[idxGin] = amount35;
  RecipeGingerGin[idxGib] = amount55;
    
  initRecipe(RecipeGingerWhiskey);
  RecipeGingerWhiskey[idxWhi] = amount35;
  RecipeGingerWhiskey[idxGib] = amount55;
  
  initRecipe(RecipeScrewdriver);
  RecipeScrewdriver[idxVod] = amount20;
  RecipeScrewdriver[idxOju] = amount40;
  
  initRecipe(RecipeKeyWestScrewdriver);
  RecipeKeyWestScrewdriver[idxVod] = amount20;
  RecipeKeyWestScrewdriver[idxOju] = amount40;
  RecipeKeyWestScrewdriver[idxLim] = amount05;
  
  initRecipe(RecipeCubaLibre);
  RecipeCubaLibre[idxRum] = amount15;
  RecipeCubaLibre[idxCok] = amount50;
  RecipeCubaLibre[idxLim] = amount05;
  
  initRecipe(RecipeBrassMonkey);
  RecipeBrassMonkey[idxVod] = amount10;
  RecipeBrassMonkey[idxRum] = amount10;
  RecipeBrassMonkey[idxOju] = amount40;
  
  initRecipe(RecipeGinAndJuice);
  RecipeGinAndJuice[idxGin] = amount15;
  RecipeGinAndJuice[idxOju] = amount40;
  
  initRecipe(RecipePanGalactic);
  RecipePanGalactic[idxVod] = amount10;
  RecipePanGalactic[idxRum] = amount05;
  RecipePanGalactic[idxWhi] = amount05;
  RecipePanGalactic[idxGin] = amount10;
  RecipePanGalactic[idxOju] = amount10;
  RecipePanGalactic[idxLim] = amount15;
  RecipePanGalactic[idxCok] = amount10;
  RecipePanGalactic[idxGib] = amount05;
  
  initRecipe(RecipeArmySpecial);
  RecipeArmySpecial[idxVod] = amount10;
  RecipeArmySpecial[idxGin] = amount10;
  RecipeArmySpecial[idxLim] = amount30;
  
  initRecipe(RecipeHotDamn);
  RecipeHotDamn[idxVod] = amount10;
  RecipeHotDamn[idxRum] = amount10;
  RecipeHotDamn[idxWhi] = amount05;
  RecipeHotDamn[idxOju] = amount35;
  
    initRecipe(RecipeCowBoyRoy);
  RecipeCowBoyRoy[idxVod] = amount20;
  RecipeCowBoyRoy[idxRum] = amount20;
  RecipeCowBoyRoy[idxOju] = amount40;

  initRecipe(RecipeGimlet);
  RecipeGimlet[idxGin] = amount40;
  RecipeGimlet[idxLim] = amount30;
  
  
  initRecipe(RecipePolo); 
  RecipePolo[idxGin] = amount20;
  RecipePolo[idxLim] = amount20;
  RecipePolo[idxOju] = amount20;
  
  initRecipe(RecipePeltikatto);
  RecipePeltikatto[idxVod] = amount20;
  RecipePeltikatto[idxLim] = amount35;
  
  initRecipe(RecipeSouthBank);
  RecipeSouthBank[idxRum] = amount10;
  RecipeSouthBank[idxGin] = amount10;
  
  // Vodka on the rocks
  initRecipe(RecipeVodkaOnTheRocks);
  RecipeVodkaOnTheRocks[idxVod] = amount20;
  
  // Rum on the rocks
  initRecipe(RecipeRumOnTheRocks);
  RecipeRumOnTheRocks[idxRum] = amount20;
  
  // Whiskey on the rocks
  initRecipe(RecipeWhiskeyOnTheRocks);
  RecipeWhiskeyOnTheRocks[idxWhi] = amount20;
  
  // Gin on the rocks
  initRecipe(RecipeGinOnTheRocks);
  RecipeGinOnTheRocks[idxGin] = amount20;
  
}

//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
void debugDisplayOrder(const string& drink, const unsigned char* pRecipe)
{
  cout << drink << endl;
  for (int i = 0; i < 11; i++)
  {
    printf ("%02X ", pRecipe[i]);
  }
  cout << endl;
}

//------------------------------------------------------------------------------
// Return
//  1 - started drink!
// -1 - failed
//------------------------------------------------------------------------------
int main(int argc, char** pArgv)
{
  if (argc != 2)
  {
    cout << "Please provide a drink" << endl;
    return -1;
  }
  
  initDrinks();
  
  const unsigned char* pRecipe = 0;

  // Verify drink name and select recipe
  string drink = string(pArgv[1]);
  
  if (drink.compare("debugAll05") == 0)
  { 
    pRecipe = RecipeDebugAll05;
  }
  else if (drink.compare("debugAll10") == 0)
  { 
    pRecipe = RecipeDebugAll10;
  }
  else if (drink.compare("debugVodka") == 0)
  { 
    pRecipe = RecipeDebugVodka;
  }
  else if (drink.compare("debugRum") == 0)
  { 
    pRecipe = RecipeDebugRum;
  }
  else if (drink.compare("debugWhiskey") == 0)
  { 
    pRecipe = RecipeDebugWhiskey;
  }
  else if (drink.compare("debugGin") == 0)
  { 
    pRecipe = RecipeDebugGin;
  }
  else if (drink.compare("debugOj") == 0)
  { 
    pRecipe = RecipeDebugOj;
  }
  else if (drink.compare("debugLime") == 0)
  { 
    pRecipe = RecipeDebugLime;
  }
  else if (drink.compare("debugCoke") == 0)
  { 
    pRecipe = RecipeDebugCoke;
  }
  else if (drink.compare("debugGingerBeer") == 0)
  { 
    pRecipe = RecipeDebugGingerBeer;
  }
  else if (drink.compare("screwdriver") == 0)
  { 
    pRecipe = RecipeScrewdriver;
  }
  else if (drink.compare("keyWestScrewdriver") == 0)
  { 
    pRecipe = RecipeKeyWestScrewdriver;
  }
  else if (drink.compare("moscowMule") == 0)
  { 
    pRecipe = RecipeMoscowMule;
  }
  else if (drink.compare("brassMonkey") == 0)
  {
    pRecipe = RecipeBrassMonkey;
  }
   else if (drink.compare("darkAndStormy") == 0)
  {
    pRecipe = RecipeDarkAndStormy;
  }
  else if (drink.compare("cubaLibre") == 0)
  {
    pRecipe = RecipeCubaLibre;
  }
  else if (drink.compare("ginAndJuice") == 0)
  {
    pRecipe = RecipeGinAndJuice;
  }
  else if (drink.compare("rumAndCoke") == 0)
  {
    pRecipe = RecipeRumAndCoke;
  }
  else if (drink.compare("vodkaOnTheRocks") == 0)
  {
    pRecipe = RecipeVodkaOnTheRocks;
  }
  else if (drink.compare("rumOnTheRocks") == 0)
  {
    pRecipe = RecipeRumOnTheRocks;
  }
  else if (drink.compare("whiskeyOnTheRocks") == 0)
  {
    pRecipe = RecipeWhiskeyOnTheRocks;
  }
  else if (drink.compare("ginOnTheRocks") == 0)
  {
    pRecipe = RecipeGinOnTheRocks;
  }
  else if (drink.compare("panGalacticGargleBlaster") == 0)
  {
    pRecipe = RecipePanGalactic;
  }
  else if (drink.compare("armySpecial") == 0){
    pRecipe = RecipeArmySpecial;
  }
    else if (drink.compare("gingerGin") == 0){
    pRecipe = RecipeGingerGin;
  }
    else if (drink.compare("gingerWhiskey") == 0){
    pRecipe = RecipeGingerWhiskey;
  }
  else if (drink.compare("hotDamn") == 0) { 
    pRecipe = RecipeHotDamn;
    }
  else if (drink.compare("cowBoyRoy") == 0) {
    pRecipe = RecipeCowBoyRoy;
    }
  else if (drink.compare("gimlet") == 0){
    pRecipe = RecipeGimlet; 
    }
  else if (drink.compare("polo") == 0){
    pRecipe = RecipePolo; 
    }
   else if (drink.compare("peltikatto") == 0){
    pRecipe = RecipePeltikatto; 
    }
  else if (drink.compare("southBank") == 0){
    pRecipe = RecipeSouthBank; 
    }
    
  else
  {
    cout << drink << "is not on the menu\n" << endl;
    return 0;
  }

  debugDisplayOrder(drink, pRecipe);

  int status = 0;
  
  //---------------------------------------
  // Serial port setup
  //---------------------------------------
  status = openPort();
  
  if ((fd == 0) || (status == 0))
  {
    cout << "Exiting because serial port could not be opened\n";
    return -1;
  }
  
  struct termios options;
  
  // Get the current options for the port...
  tcgetattr(fd, &options);

  // Set serial speed
  // B9600      9600 baud
  // B19200    19200 baud
  // B38400    38400 baud
  // B57600   57,600 baud
  // B76800   76,800 baud
  // B115200 115,200 baud
  cfsetispeed(&options, B9600);
  cfsetospeed(&options, B9600);
  
  // No parity (8N1)
  options.c_cflag &= ~PARENB;
  options.c_cflag &= ~CSTOPB;
  options.c_cflag &= ~CSIZE;
  options.c_cflag |= CS8;

  // Enable the receiver and set local mode...
  options.c_cflag |= (CLOCAL | CREAD);

  // Set the new options for the port...
  tcsetattr(fd, TCSANOW, &options);
  
  // Non-Blocking
  fcntl(fd, F_SETFL, FNDELAY);

  //---------------------------------------
  // Main logic starts here!
  //---------------------------------------
  
  cout << "Check system status....";
  status = isReady();
  printStatus(status);
  cout << endl;
  
  // Send drink recipe
  if (status == ack)
  {
    cout << "Sending recipe.........";
    
    write(fd, pRecipe, recipeLength);
    
    status = readStatus(5000);
    printStatus(status);
    cout << endl;
    
    closePort();
    return 1;
  }

  closePort();
  return -1;
}

